package com.bdqn.qnnews.minio.utils;

import com.bdqn.qnnews.minio.config.MinioQnProperties;
import io.minio.GetObjectArgs;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.RemoveObjectArgs;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @author xlzhang
 * @date 2023/5/27 22:25
 * minio文件存储工具
 */
@Slf4j
public class MinioUtil {
    // 路径分隔符
    private final static String SEPARATOR = "/";
    private final MinioClient minioClient;
    private final MinioQnProperties prop;

    //构造函数
    public MinioUtil(MinioClient minioClient, MinioQnProperties prop) {
        this.minioClient = minioClient;
        this.prop = prop;
    }

    /**
     * @param dirPath 自定义文件目录
     * @param filename 文件名称，比如： file.xxx
     * @return 添加了日期目录的完整文件目录
     * 例子： dirPath/yyyy/MM/dd/file.xxx
     */
    public String builderFilePath(String dirPath,String filename) {
        StringBuilder stringBuilder = new StringBuilder(50);
        // 如果有指定的自定义目录
        if(StringUtils.hasLength(dirPath)){
            stringBuilder.append(dirPath).append(SEPARATOR);
        }
        // 将日期转换为指定格式的字符串(可以直接作为文件目录)
        String todayStr = new SimpleDateFormat("yyyy/MM/dd").format(new Date());
        // 将日期字符串拼接到目录中
        // 2023/12/23/xxx.jpg
        stringBuilder.append(todayStr).append(SEPARATOR);
        // 最后拼接文件名称
        stringBuilder.append(filename);
        // 返回拼接好的文件目录字符串
        return stringBuilder.toString();
    }

    /**
     *  上传图片文件
     * @param prefix  文件前缀(相当于文件前面的父目录)
     * @param filename  文件名
     * @param inputStream 文件流
     * @return  上传成功后的文件全路径
     */
    public String uploadImgFile(String prefix, String filename, InputStream inputStream) {
        return uploadFile(prefix,filename,"image/jpg",inputStream);
    }

    /**
     *  上传html文件
     * @param prefix  文件前缀(相当于文件前面的父目录)
     * @param filename  文件名
     * @param inputStream 文件流
     * @return  上传成功后的文件全路径
     */
    public String uploadHtmlFile(String prefix, String filename, InputStream inputStream) {
        return uploadFile(prefix,filename,"text/html",inputStream);
    }

    /**
     *  上传文件
     * @param prefix  文件前缀(相当于文件前面的父目录)
     * @param filename  文件名
     * @param contentType  文件媒体类型
     * @param in 文件流
     * @return  上传成功后的文件全路径
     */
    public String uploadFile(String prefix, String filename, String contentType, InputStream in) {
        // 1. 构建文件目录
        String filePath = builderFilePath(prefix, filename);
        try {
            // 2. 构造一个上传文件对象, stream(): 需要上传内容
            PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                    .object(filePath)
                    .contentType(contentType)
                    .bucket(prop.getBucket())
                    .stream(in,in.available(),-1)
                    .build();
            // 3. 执行文件上传
            minioClient.putObject(putObjectArgs);
            // 4. 拼接上传成功后的文件全路径
            // http://10.0.0.5:9001/qnnews/yyyy/MM/dd/file.jpg
            return prop.getReadPath() + SEPARATOR + prop.getBucket() + SEPARATOR + filePath;
        }catch (Exception ex){
            log.error("minio put file error.",ex);
            throw new RuntimeException("上传文件失败");
        }finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 删除文件
     * @param pathUrl  文件全路径
     * http://10.0.0.5:9001/qnnews/yyyy/MM/dd/file.jpg
     */
    public void delete(String pathUrl) {
        // 1. 先去除前面的host，得到文件的uri ，
        // 如 qnnews/yyyy/MM/dd/file.xxx
        String fileUri = pathUrl.replace(prop.getEndpoint()+"/","");
        // 2. 切割获取第一段目录：桶名称
        // qnnews/yyyy/MM/dd/file.jpg
        final int index = fileUri.indexOf(SEPARATOR);
        String bucket = fileUri.substring(0, index);
        // 3. 从第一个路径分割符开始切割获得后面的 yyyy/MM/dd/file.xxx
        String filePath = fileUri.substring(index + 1);
        // 删除Objects
        RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder()
                .bucket(bucket)
                //在Bucket中的文件目录名称 yyyy/MM/dd/file.xxx
                .object(filePath)
                .build();
        try {
            // 执行删除
            minioClient.removeObject(removeObjectArgs);
        } catch (Exception e) {
            log.error("minio remove file error.  pathUrl:{}",pathUrl);
            throw new RuntimeException(e);
        }
    }

    /**
     * 下载文件
     * @param pathUrl  文件全路径
     * @return  文件流
     */
    public byte[] downLoadFile(String pathUrl)  {
        // 1. 先去除前面的host，得到文件的uri ，
        // 如 qnnews/yyyy/MM/dd/file.xxx
        String fileUri = pathUrl.replace(prop.getEndpoint()+"/","");
        // 2. 切割获取第一段目录：桶名称
        final int index = fileUri.indexOf(SEPARATOR);
        String bucket = fileUri.substring(0, index);
        // 3. 从第一个路径分割符开始切割获得后面的 yyyy/MM/dd/file.xxx
        String filePath = fileUri.substring(index + 1);

        InputStream in = null;
        ByteArrayOutputStream out = null;
        try {
            // 从minio中下载文件，返回输入流对象
            in = minioClient.getObject(GetObjectArgs.builder()
                    .bucket(bucket)
                    .object(filePath)
                    .build());
            // 创建一个二进制数组输出流，将文件以二进制数组的方式返回给调用者
            out = new ByteArrayOutputStream();
            byte[] buff = new byte[100];
            int rc = 0;
            while ((rc = in.read(buff)) != -1) {
                out.write(buff, 0, rc);
            }
            // 返回二进制数组
            return out.toByteArray();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (Exception e) {
            log.error("minio down file error.  pathUrl:{}",pathUrl);
            throw new RuntimeException(e);
        }finally {
            try {
                if (out != null){
                    out.close();
                }
                if (in != null){
                    in.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}